#include "EZSocketFD.h"
#include "loop/EZLoop.h"
#include "common/EZLog.h"
#include "event.h"
#include "event2/util.h"
#include <assert.h>

namespace EZTcp{

static_assert(EZSocketFD::kReadable == EV_READ, "");
static_assert(EZSocketFD::kWritable == EV_WRITE, "");


EZSocketFD::EZSocketFD(EZLoop* loop, ez_socket_t fd
	, bool watch_read_event, bool watch_write_event)
	: loop_(loop), attached_(false), event_(nullptr), fd_(fd) 
{
    events_ = (watch_read_event ? kReadable : 0) | (watch_write_event ? kWritable : 0);
	event_ = new event;
	memset(event_, 0, sizeof(struct event));
}


EZSocketFD::~EZSocketFD()
{
	assert(event_ == nullptr);
}

void EZSocketFD::destroy()
{
	if (event_) {
		if (attached_) {
			event_del(event_);
		}
        attached_ = false;
		delete (event_);
		event_ = nullptr;
        read_fn_ = ReadEventCallback();
        write_fn_ = EventCallback();
	}
}

void EZSocketFD::attach_to_loop()
{
	assert(!is_none_event());
	assert(loop_->is_in_loop_thread());

	if (attached_) {
		// FdChannel::Update may be called many times
		// So doing this can avoid event_add will be called more than once.
		_detach_from_loop();
	}

	assert(!attached_);

	::event_set(event_, fd_, events_ | EV_PERSIST,
		&EZSocketFD::HandleEvent, this);
	::event_base_set(loop_->event_base(), event_);

	if (event_add(event_, nullptr) == 0) {
		attached_ = true;
	}
	else {
		EZLOG_ERROR << "this=" << this << " fd=" << fd_ << " with event " << events_to_string() << " attach to event loop failed";
	}
}

void EZSocketFD::enable_read_event()
{
	int events = events_;
	events_ |= kReadable;

	if (events_ != events) {
		_update();
	}
}

void EZSocketFD::enable_write_event()
{
	int events = events_;
	events_ |= kWritable;

	if (events_ != events) {
		_update();
	}
}

void EZSocketFD::disable_read_event()
{
	int events = events_;
	events_ &= (~kReadable);

	if (events_ != events) {
		_update();
	}
}

void EZSocketFD::disable_write_event()
{
	int events = events_;
	events_ &= (~kWritable);
	if (events_ != events) {
		_update();
	}
}

void EZSocketFD::disable_all_event()
{
	if (events_ == kNone) {
		return;
	}

	events_ = kNone;
	_update();
}

std::string EZSocketFD::events_to_string() const
{
	std::string s;

	if (events_ & kReadable) {
		s = "kReadable";
	}

	if (events_ & kWritable) {
		if (!s.empty()) {
			s += "|";
		}
		s += "kWritable";
	}

	return s;
}

void EZSocketFD::HandleEvent(ez_socket_t fd, short which, void* v)
{
	EZSocketFD* c = (EZSocketFD*)v;
	c->HandleEvent(fd, which);
}

void EZSocketFD::HandleEvent(ez_socket_t fd, short which)
{
	if ((which & kReadable) && read_fn_) {
		read_fn_();
	}

	if ((which & kWritable) && write_fn_) {
		write_fn_();
	}
}

void EZSocketFD::_update()
{
	assert(loop_->is_in_loop_thread());

	if (is_none_event()) {
		_detach_from_loop();
	}
	else {
		attach_to_loop();
	}
}

void EZSocketFD::_detach_from_loop()
{
	assert(loop_->is_in_loop_thread());
	assert(attached_);

	if (event_del(event_) == 0) {
		attached_ = false;
	}
	else {
		EZLOG_ERROR << "DetachFromLoop this=" << this << "fd=" << fd_ 
            << " with event " << events_to_string() << " detach from event loop failed";
	}
}

}
